//------------------------------------------------------------------------------
//!	@file XpanderSysEx.c
//!	@brief
//!		XpanderSysEx processing file
//!
//!	$Id: $
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
//	Copyright and Disclaimer Notice
//
//	Copyright (c) 2013 A.C. Verbeck
//	original author: Greg Baumont - http://greg-baumont.com
//
//	This file is part of XSPV: Oberheim Xpander Single Patch Viewer.
//
//	XSPV is free software: you can redistribute it and/or modify
//	it under the terms of the GNU General Public License as published by
//	the Free Software Foundation, either version 3 of the License, or
//	(at your option) any later version.
//
//	XSPV is distributed in the hope that it will be useful,
//	but WITHOUT ANY WARRANTY; without even the implied warranty of
//	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//	GNU General Public License for more details.
//
//	You should have received a copy of the GNU General Public License
//	along with XSPV.  If not, see <http://www.gnu.org/licenses/>.
//
//------------------------------------------------------------------------------
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>

#include <stdint.h>																//	Standard int types
#include <stdbool.h>															//	Bool

#include "XpanderSysEx.h"

//------------------------------------------------------------------------------
// Local defines
//------------------------------------------------------------------------------
static const int SYSEX_INTRO				= 6;								// 6 bytes for sysex intro: F0 10 02....
static const int PATCHNAME_LENGTH			= 8;								// 188 double bytes data for the values and 8 double bytes for the patch name
static const int OBWORDS_DATA_LENGTH		= 188;								// 6 (intro) +196*2 (data+name) +1 (EOX) = 399

static const int MOD_SRC_CT					= 27;								// 27 modulation sources
static const int MOD_DEST_CT				= 47;								// 47 modulation destinations
static const int MOD_MAX_CT					= 20;								// only 20 modulations entries per patch

static const int MOD_FLAGS_CT				= 4;
static const int VCO_WF_FLAGS_CT			= 5;
static const int VCF_FILTER_CT				= 15;
static const int FM_DEST_CT					= 2;
static const int LAG_FLAG_CT				= 3;
static const int TRIGGER_CT					= 4;
static const int WAVE_CT					= 7;
static const int LAG_CT						= 1;
static const int ENV_MODES_CT				= 8;
static const int LFO_TRIGGER_CT				= 6;
static const int RAMP_CT					= 4;

static const char* SINGLE_LINE="---------------------------\n";
static const char* DOUBLE_LINE="===========================\n";
static const uint8_t sysex[] = {0xf0,0x10,0x02,0x01,0x00,0x00};

//------------------------------------------------------------------------------
// Local typedefs
//------------------------------------------------------------------------------
typedef struct  _NumberStringPair {
	unsigned char m_iNumber;
	const char* pszString;
} NumberStringPair;

//------------------------------------------------------------------------------
// Modulation Flags
//------------------------------------------------------------------------------
static const NumberStringPair ModulationFlagsNames[] = {
	{MODFLAG_KEYBD,"KEYBD"},
	{MODFLAG_LAG,"LAG"},
	{MODFLAG_LEV_1,"LEV_1"},
	{MODFLAG_VIB,"VIB"}
};

//------------------------------------------------------------------------------
// VCO Wave Flags
//------------------------------------------------------------------------------
static const NumberStringPair VCOWavesFlagsNames[] = {
	{VCOWAVEFLAG_TRI,"TRI"},
	{VCOWAVEFLAG_SAW,"SAW"},
	{VCOWAVEFLAG_PULSE,"PULSE"},
	{VCOWAVEFLAG_SYNC,"SYNC"},
	{VCOWAVEFLAG_NOISE,"NOISE"}
};

//------------------------------------------------------------------------------
// VCF Filter Types
//------------------------------------------------------------------------------
static const char* VCFFilterTypesNames[]= {
	"LOW_1","LOW_2","LOW_3","LOW_4",
	"HIGH_1","HIGH_2","HIGH_3",
	"BAND_2","BAND_4",
	"NOTCH_2",
	"PHASE_3",
	"HIGH_2L","HIGH_3L",
	"NOTCH_2L",
	"PHASE_3L"
};

//------------------------------------------------------------------------------
// FM Destination Types
//------------------------------------------------------------------------------
static const char* FMDestinationTypesNames[]= {
	"VCO1_FREQ",
	"VCF_FREQ"
};

//------------------------------------------------------------------------------
// Modulation Sources Flags
//------------------------------------------------------------------------------
static const char* ModulationSourcesFlagsNames[] = {
	"KBD","LAG","VEL","RVEL","PRES",
	"TRK1","TRK2","TRK3",
	"RMP1","RMP2","RMP3","RMP4",
	"ENV1","ENV2","ENV3","ENV4","ENV5",
	"PED1","PED2",
	"LFO1","LFO2","LFO3","LFO4","LFO5",
	"VIB","LEV1","LEV2"
};

//------------------------------------------------------------------------------
// Lag Mode Flags
//------------------------------------------------------------------------------
static const NumberStringPair LagModeFlagsNames[] = {
	{LAGMODE_LEGATO,"LEGATO"},
	{LAGMODE_EXPO,"EXPO"},
	{LAGMODE_EQUAL_TIME,"EQUAL_TIME"},
};

//------------------------------------------------------------------------------
// Trigger Types
//------------------------------------------------------------------------------
static const char* TriggerTypesNames[] = {
	"OFF","SINGLE","MULTI", "EXTRIG"
};

//------------------------------------------------------------------------------
// Wave Types
//------------------------------------------------------------------------------
static const char* WaveTypesNames[] = {
	"TRIANGLE", "UP_SAW", "DOWN_SAW","SQUARE","RANDOM","NOISE","SAMPLE"
};

//------------------------------------------------------------------------------
// Lag Flags
//------------------------------------------------------------------------------
static const NumberStringPair LagFlagsNames[] = {
	{LAGF_LAG,"LAG"}
};

//------------------------------------------------------------------------------
// Envelope Mode Flags (bitfield)
//------------------------------------------------------------------------------
static const NumberStringPair EnveloppeModeFlagsNames[] = {
	{ENVMODE_RESET,"RESET"},
	{ENVMODE_INVALID_VALUE,"INVALID VALUE !"},
	{ENVMODE_MULTI,"MULTI"},
	{ENVMODE_GATED,"GATED"},
	{ENVMODE_EXTRIG,"EXTRIG"},
	{ENVMODE_LFOTRIG,"LFOTRIG"},
	{ENVMODE_DADR,"DADR"},
	{ENVMODE_FREERUN,"FREERUN"}
};

//------------------------------------------------------------------------------
// LFO Trigger Codes
//------------------------------------------------------------------------------
static const char* LFOTriggerCodesNames[] = {
	"LFO1", "LFO2", "LFO3","LFO4","LFO5","VIB"
};

//------------------------------------------------------------------------------
// Ramp Flags
//------------------------------------------------------------------------------
static const NumberStringPair RampFlagsNames[] = {
	{RAMPF_GATED,"GATED"},
	{RAMPF_LFOTRIG,"LFOTRIG"},
	{RAMPF_EXTRIG,"EXTRIG"},
	{RAMPF_MULTI,"MULTI"}
};

//------------------------------------------------------------------------------
// Modulation Destination Types
//------------------------------------------------------------------------------
static const char* ModulationDestinationsTypesNames[] = {
	"VCO1_FRQ", "VCO1_PW", "VCO1_VOL",
	"VCO2_FRQ", "VCO2_PW"," VCO2_VOL",
	"VCF_FRQ",  "VCF_RES","VCA1_VOL","VCA2_VOL",								//10
	"LFO1_SPD", "LFO1_AMP",
	"LFO2_SPD", "LFO2_AMP",
	"LFO3_SPD", "LFO3_AMP",
	"LFO4_SPD", "LFO4_AMP",
	"LFO5_SPD", "LFO5_AMP",														//20
	"ENV1_DLY", "ENV1_ATK", "ENV1_DCY","ENV1_REL","ENV1_AMP",
	"ENV2_DLY", "ENV2_ATK", "ENV2_DCY","ENV2_REL","ENV2_AMP", 					//30
	"ENV3_DLY", "ENV3_ATK", "ENV3_DCY","ENV3_REL","ENV3_AMP",
	"ENV4_DLY", "ENV4_ATK", "ENV4_DCY","ENV4_REL","ENV4_AMP", 					//40
	"ENV5_DLY", "ENV5_ATK", "ENV5_DCY","ENV5_REL","ENV5_AMP",					//45
	"FM_AMP","LAG_SPD"															//47
};


//------------------------------------------------------------------------------
//!	@brief
//!		Locate a single patch
//!		Locate a single patch sysex data from the specified file
//!
//!	@param
//!		[in] pFile: the opened file to get data from
//!
//!	@remark
//!		when data are found, current file position is set to the beginning of the data.
//!
//!	@return
//!		true is single patch data found, else false.
//------------------------------------------------------------------------------
bool LocateSinglePatchData(FILE* pFile)
{

	bool bSinglePatchDataFound = false;
	bool bEndOfFile = false;

	unsigned char intro[SYSEX_INTRO];

	while (!bSinglePatchDataFound && !bEndOfFile) {

		memset(intro,0,SYSEX_INTRO);
		// try to identify Single Patch data: F0 10 02 01 00...
		int nbBytesRead = fread(intro,sizeof(char),SYSEX_INTRO,pFile);
		if (nbBytesRead!=SYSEX_INTRO) {
			bEndOfFile=true;
			break;
		}
		if ((intro[0]==0xF0) && (intro[1]==0x10) && (intro[2]==0x02) && (intro[3]==0x01) && (intro[4]==0x00)) {
			// bingo...
			fprintf(stdout,DOUBLE_LINE);
			fprintf(stdout,"Program type:\t %02Xh\nProgram number:\t %02Xh (%02d)\n",intro[4],intro[5],intro[5]);
			{
				bSinglePatchDataFound=true;
				break;
			}
		} else {
			// try one byte after beginning...
			fseek(pFile,-(SYSEX_INTRO-1),SEEK_CUR);
		}
	}

	return (!bEndOfFile);
}


//------------------------------------------------------------------------------
//!	@brief
//!		Read the the single patch data from file to a SinglePatch struct
//!
//!	@param
//!		[in] pFile: the opened file to get data from
//!
//!	@param
//!		[out] pPatch: the single patch data struct
//!
//!	@remark
//!		assumes that the file current position is at the beginning of the data
//!
//!	@return
//!		none
//------------------------------------------------------------------------------
void ReadSinglePatchData(FILE* pFile, SinglePatch* pPatch)
{

	//  data in sysex are double bytes values
	unsigned short shortArray[OBWORDS_DATA_LENGTH];
	memset(shortArray,0,OBWORDS_DATA_LENGTH);

	int iReadBytes=0;
	iReadBytes=fread(shortArray,sizeof(short),OBWORDS_DATA_LENGTH,pFile);

	assert(iReadBytes==OBWORDS_DATA_LENGTH);

	// for each double byte, repack the value, and set the Patch property accordingly
	unsigned char* pByte= (unsigned char*)pPatch;
	for (int i=0; i<OBWORDS_DATA_LENGTH; i++) {
		// 8th bit of the 8 bits value is the first bit of the high byte
		unsigned char cValue = ((shortArray[i] & 0x0100)>>1) | (shortArray[i] & 0x00ff);
		*pByte=cValue;
		pByte++;
	}

	// convert the modulation matrix amount values (6 bits 2's complement + extension of the sign bit)
	for (int i=0; i<MOD_MAX_CT; i++) {
		char amount = pPatch->mod[i].amount;
		// detect the sign bit (ex: for -1, Xpander sends 0x00 0x41: 0100 0001)
		if ((amount & 0x40) == 0x40) {
			// add a sign bit to comply with 8 bits signed char -> 1100 0001
			amount = amount | 0x80;
			//2's complement - reverse the 6 bits and add one -> 1111 1111
			amount = amount^0x3F;
			amount+=0x01;
			pPatch->mod[i].amount=amount;
		}
	}

	// name is 2 bytes/char, high byte never used, thus wchar_t compatible
	iReadBytes=0;
	iReadBytes=fread(&pPatch->name,sizeof(wchar_t),PATCHNAME_LENGTH,pFile);
	assert(iReadBytes==PATCHNAME_LENGTH);

}

//------------------------------------------------------------------------------
//!	@brief
//!		Dump a SinglePatch struct to stdout in human-readable format
//!
//!	@param
//!		[in] pPatch: patch to dump
//!
//!	@return
//!		none
//------------------------------------------------------------------------------
void DumpPatch(const SinglePatch* pPatch)
{

//------------------------------------------------------------------------------
// Patch Name
//------------------------------------------------------------------------------
	fprintf(stdout,"NAME:\t%S\n",&pPatch->name);

//------------------------------------------------------------------------------
// VCO-1 parameters
//------------------------------------------------------------------------------
	fprintf(stdout,SINGLE_LINE);
	fprintf(stdout,"VCO1.freq:\t %02Xh\t %4d\n",pPatch->vco[0].freq,pPatch->vco[0].freq);
	fprintf(stdout,"VCO1.detune:\t %02Xh\t %4d\n",pPatch->vco[0].detune,pPatch->vco[0].detune);
	fprintf(stdout,"VCO1.pw:\t %02Xh\t %4d\n",pPatch->vco[0].pw,pPatch->vco[0].pw);
	fprintf(stdout,"VCO1.vol:\t %02Xh\t %4d\n",pPatch->vco[0].vol,pPatch->vco[0].vol);
	fprintf(stdout,"VCO1.mod:\t %02Xh\t %4d : ",pPatch->vco[0].mod,pPatch->vco[0].mod);
	// show bitfields
	for (int i=0; i<MOD_FLAGS_CT; i++) {
		if ( (ModulationFlagsNames[i].m_iNumber & pPatch->vco[0].mod)== ModulationFlagsNames[i].m_iNumber) {
			fprintf(stdout,"%s ",ModulationFlagsNames[i].pszString);
		}
	}
	fprintf(stdout,"\n");
	fprintf(stdout,"VCO1.wave:\t %02Xh\t %4d : ",pPatch->vco[0].wave,pPatch->vco[0].wave);
	for (int i=0; i<VCO_WF_FLAGS_CT; i++) {
		if ( (VCOWavesFlagsNames[i].m_iNumber & pPatch->vco[0].wave)== VCOWavesFlagsNames[i].m_iNumber) {
			fprintf(stdout,"%s ",VCOWavesFlagsNames[i].pszString);
		}
	}
	fprintf(stdout,"\n");

//------------------------------------------------------------------------------
// VCO-2 parameters
//------------------------------------------------------------------------------
	fprintf(stdout,SINGLE_LINE);
	fprintf(stdout,"VCO2.freq:\t %02Xh\t %4d\n",pPatch->vco[1].freq,pPatch->vco[1].freq);
	fprintf(stdout,"VCO2.detune:\t %02Xh\t %4d\n",pPatch->vco[1].detune,pPatch->vco[1].detune);
	fprintf(stdout,"VCO2.pw:\t %02Xh\t %4d\n",pPatch->vco[1].pw,pPatch->vco[1].pw);
	fprintf(stdout,"VCO2.vol:\t %02Xh\t %4d\n",pPatch->vco[1].vol,pPatch->vco[1].vol);
	fprintf(stdout,"VCO2.mod:\t %02Xh\t %4d : ",pPatch->vco[1].mod,pPatch->vco[1].mod);
	for (int i=0; i<4; i++) {
		if ( (ModulationFlagsNames[i].m_iNumber & pPatch->vco[1].mod)== ModulationFlagsNames[i].m_iNumber) {
			fprintf(stdout,"%s ",ModulationFlagsNames[i].pszString);
		}
	}
	fprintf(stdout,"\n");
	fprintf(stdout,"VCO2.wave:\t %02Xh\t %4d : ",pPatch->vco[1].wave,pPatch->vco[1].wave);
	for (int i=0; i<VCO_WF_FLAGS_CT; i++) {
		if ( (VCOWavesFlagsNames[i].m_iNumber & pPatch->vco[1].wave)== VCOWavesFlagsNames[i].m_iNumber) {
			fprintf(stdout,"%s ",VCOWavesFlagsNames[i].pszString);
		}
	}
	fprintf(stdout,"\n");

//------------------------------------------------------------------------------
// VCF parameters
//------------------------------------------------------------------------------
	fprintf(stdout,SINGLE_LINE);
	fprintf(stdout,"VCF.freq:\t %02Xh\t %4d\n", pPatch->vcf.freq,pPatch->vcf.freq);
	fprintf(stdout,"VCF.res:\t %02Xh\t %4d\n",pPatch->vcf.res,pPatch->vcf.res);
	fprintf(stdout,"VCF.mode:\t %02Xh\t %4d : %s\n" ,pPatch->vcf.fmode,pPatch->vcf.fmode,VCFFilterTypesNames[pPatch->vcf.fmode]);
	fprintf(stdout,"VCF.vca1:\t %02Xh\t %4d\n" ,pPatch->vcf.vca1,pPatch->vcf.vca1);
	fprintf(stdout,"VCF.vca2:\t %02Xh\t %4d\n" ,pPatch->vcf.vca2,pPatch->vcf.vca2);
	fprintf(stdout,"VCF.mod:\t %02Xh\t %4d : ",pPatch->vcf.mod,pPatch->vcf.mod);
	for (int i=0; i<MOD_FLAGS_CT; i++) {
		if ( (ModulationFlagsNames[i].m_iNumber & pPatch->vcf.mod)== ModulationFlagsNames[i].m_iNumber) {
			fprintf(stdout,"%s ",ModulationFlagsNames[i].pszString);
		}
	}
	fprintf(stdout,"\n");

//------------------------------------------------------------------------------
// FM LAG parameters
//------------------------------------------------------------------------------
	fprintf(stdout,SINGLE_LINE);

	fprintf(stdout,"FMLAG.amp\t %02Xh\t %4d\n", pPatch->fm_lag.fm_amp,pPatch->fm_lag.fm_amp);
	fprintf(stdout,"FMLAG.dest:\t %02Xh\t %4d : %s\n", pPatch->fm_lag.fm_dest,pPatch->fm_lag.fm_dest,FMDestinationTypesNames[pPatch->fm_lag.fm_dest]);
	fprintf(stdout,"FMLAG.lag_in:\t %02Xh\t %4d : %s\n", pPatch->fm_lag.lag_in,pPatch->fm_lag.lag_in,ModulationSourcesFlagsNames[pPatch->fm_lag.lag_in]);
	fprintf(stdout,"FMLAG.lag_rate:\t %02Xh\t %4d\n",pPatch->fm_lag.lag_rate,pPatch->fm_lag.lag_rate);
	fprintf(stdout,"FMLAG.lag_mode:\t %02Xh\t %4d : " ,pPatch->fm_lag.lag_mode,pPatch->fm_lag.lag_mode);
	for (int i=0; i<LAG_FLAG_CT; i++) {
		if ( (LagModeFlagsNames[i].m_iNumber & pPatch->fm_lag.lag_mode)== LagModeFlagsNames[i].m_iNumber) {
			fprintf(stdout,"%s ",LagModeFlagsNames[i].pszString);
		}
	}
	fprintf(stdout,"\n");

//------------------------------------------------------------------------------
// LFO-1 to 5 parameters
//------------------------------------------------------------------------------
	for (int i=0; i<5; i++) {
		fprintf(stdout,SINGLE_LINE);
		fprintf(stdout,"LFO[%01d].speed:\t %02Xh\t %4d\n",i+1,pPatch->lfo[i].speed,pPatch->lfo[i].speed);
		fprintf(stdout,"LFO[%01d].trg_mod:\t %02Xh\t %4d : %s\n",i+1,pPatch->lfo[i].retrig_mode,pPatch->lfo[i].retrig_mode,TriggerTypesNames[pPatch->lfo[i].retrig_mode]);
		fprintf(stdout,"LFO[%01d].lag:\t %02Xh\t %4d : ",i+1,pPatch->lfo[i].lag,pPatch->lfo[i].lag);
		for (int j=0; j<LAG_CT; j++) {
			if ( (LagFlagsNames[j].m_iNumber & pPatch->lfo[i].lag)== LagFlagsNames[j].m_iNumber) {
				fprintf(stdout,"%s ",LagFlagsNames[j].pszString);
			}
		}
		fprintf(stdout,"\n");
		fprintf(stdout,"LFO[%01d].wave:\t %02Xh\t %4d : %s\n",i+1,pPatch->lfo[i].wave,pPatch->lfo[i].wave,WaveTypesNames[pPatch->lfo[i].wave]);
		fprintf(stdout,"LFO[%01d].retrig:\t %02Xh\t %4d\n",i+1,pPatch->lfo[i].retrig,pPatch->lfo[i].retrig);
		fprintf(stdout,"LFO[%01d].sample\t %02Xh\t %4d : %s\n",i+1,pPatch->lfo[i].sample,pPatch->lfo[i].sample,ModulationSourcesFlagsNames[pPatch->lfo[i].sample]);
		fprintf(stdout,"LFO[%01d].amp:\t %02Xh\t %4d\n",i+1,pPatch->lfo[i].amp,pPatch->lfo[i].amp);
	}

//------------------------------------------------------------------------------
// Envelope-1 to 5 parameters
//------------------------------------------------------------------------------
	for (int i=0; i<5; i++) {
		fprintf(stdout,SINGLE_LINE);
		fprintf(stdout,"ENV[%01d].flags:\t %02Xh\t %4d : ",i+1,pPatch->env[i].flags,pPatch->env[i].flags);

		for (int j=0; j<ENV_MODES_CT; j++) {
			if ( (EnveloppeModeFlagsNames[j].m_iNumber & pPatch->env[i].flags)== EnveloppeModeFlagsNames[j].m_iNumber) {
				fprintf(stdout,"%s ",EnveloppeModeFlagsNames[j].pszString);
			}
		}
		fprintf(stdout,"\n");

		fprintf(stdout,"ENV[%01d].lfo_trg:\t %02Xh\t %4d : %s\n",i+1,pPatch->env[i].lfotrig,pPatch->env[i].lfotrig,LFOTriggerCodesNames[pPatch->env[i].lfotrig]);
		fprintf(stdout,"ENV[%01d].delay:\t %02Xh\t %4d\n",i+1,pPatch->env[i].delay,pPatch->env[i].delay);
		fprintf(stdout,"ENV[%01d].attck:\t %02Xh\t %4d\n",i+1,pPatch->env[i].attack,pPatch->env[i].attack);
		fprintf(stdout,"ENV[%01d].decay:\t %02Xh\t %4d\n",i+1,pPatch->env[i].decay,pPatch->env[i].decay);
		fprintf(stdout,"ENV[%01d].sustain:\t %02Xh\t %4d\n",i+1,pPatch->env[i].sustain,pPatch->env[i].sustain);
		fprintf(stdout,"ENV[%01d].rel:\t %02Xh\t %4d\n",i+1,pPatch->env[i].release,pPatch->env[i].release);
		fprintf(stdout,"ENV[%01d].amp:\t %02Xh\t %4d\n",i+1,pPatch->env[i].amp,pPatch->env[i].amp);
	}

//------------------------------------------------------------------------------
// Tracking Generator-1 to 3 parameters
//------------------------------------------------------------------------------
	for (int i=0; i<3; i++) {
		fprintf(stdout,SINGLE_LINE);
		fprintf(stdout,"TRACK[%01d].input:\t %02Xh\t %4d : %s\n",i+1,pPatch->track[i].input,pPatch->track[i].input,ModulationSourcesFlagsNames[pPatch->track[i].input]);
		// track points (x5)
		fprintf(stdout,"TRACK[%01d].points:\t    ",i+1);
		for (int j=0; j<5; j++) {
			if (j<4) {
				fprintf(stdout,"%d,",pPatch->track[i].point[j]);
			} else {
				fprintf(stdout,"%d",pPatch->track[i].point[j]);
			}
		}
		fprintf(stdout,"\n");
	}

//------------------------------------------------------------------------------
// RAMP-1 to 4 parameters
//------------------------------------------------------------------------------
	for (int i=0; i<4; i++) {
		fprintf(stdout,SINGLE_LINE);
		fprintf(stdout,"RAMP[%01d].rate:\t %02Xh\t %4d\n",i+1,pPatch->ramp[i].rate,pPatch->ramp[i].rate);
		fprintf(stdout,"RAMP[%01d].flags:\t %02Xh\t %4d : ",i+1,pPatch->ramp[i].flags,pPatch->ramp[i].flags);

		for (int j=0; j<RAMP_CT; j++) {
			if ( (RampFlagsNames[j].m_iNumber & pPatch->ramp[i].flags)== RampFlagsNames[j].m_iNumber) {
				fprintf(stdout,"%s ",RampFlagsNames[j].pszString);
			}
		}
		fprintf(stdout,"\n");

		fprintf(stdout,"RAMP[%01d].lfotrg:\t %02Xh\t %4d : %s\n",i+1,pPatch->ramp[i].lfotrig,pPatch->ramp[i].lfotrig,LFOTriggerCodesNames[pPatch->ramp[i].lfotrig]);

	}

//------------------------------------------------------------------------------
// Modulation Matrix-1 to 20 parameters
//------------------------------------------------------------------------------
	for (int i=0; i<MOD_MAX_CT; i++) {
		fprintf(stdout,SINGLE_LINE);

		// seems that unused modulations entries are garbage
		char source  = pPatch->mod[i].source;
		char dest = pPatch->mod[i].dest;
		if (source>=MOD_SRC_CT || dest>=MOD_DEST_CT) {
			fprintf(stdout,"MOD[%02d]: UNUSED ENTRY\n" ,i+1);
		} else {
			fprintf(stdout,"MOD[%02d]: %s modulates %s, amount %d\n",i+1,
			        ModulationSourcesFlagsNames[pPatch->mod[i].source],ModulationDestinationsTypesNames[pPatch->mod[i].dest],pPatch->mod[i].amount);
		}
	}
}

//
//  End: XpanderSysEx.c
//
